import { defineComponent, markRaw, onUnmounted, reactive, ref, watch } from 'vue'
import ItemNode from './ItemNode'
import classes from './ItemNode.module.css'
import { ElInput, ElSwitch, ElText } from 'element-plus'
import request from '../request/request'
import requestRaw from '../request/request?raw'
import { state } from '../store/store.js'
import { debounce } from '../utils'
import CodeEditor from '../panel/CodeEditor.vue'
import Tip from '../panel/Tip.vue'

export default class DataNode extends ItemNode {
  isDataNode = true
  itemData = ref() // NList传入的item数据

  constructor(node?) {
    super(node)

    const _self = this

    this.watchStateOptions.push({
      value: 'initData',
      label: '刷新数据'
    })

    if (!node) {
      this.props.data = null
      this.props.dataUrl = ''
      this.props.dataFn = 'function () {\n  return null\n}'
      this.props.dataFnable = true
    }

    const pComponent = this.component
    this.component = markRaw(
      defineComponent((props, { slots }) => {
        // 获取数据
        const initData = async () => {
          let resData = null
          let itemData = this.itemData.value

          if (this.props.dataUrl) {
            await request({
              url: this.props.dataUrl.replace(/\:(\w+)/g, (match, p1) => state[p1]),
              method: 'GET'
            }).then((res) => (resData = res))
          }

          if (this.props.dataFn) {
            try {
              // 避免打包工具清除无引用变量，导致eval里访问不到
              if (_self || state || resData || itemData)
                this.props.data = eval(`(${this.props.dataFn})`)()
            } catch (e) {}
          }
        }
        // 编辑内容时防抖
        const initDataDebounce = debounce(() => {
          initData()
        }, 1000)
        // NList传入的itemData监听需延迟处理
        watch(
          [() => this.props.dataUrl, () => this.props.dataFn, this.itemData],
          initDataDebounce,
          { deep: true }
        )

        initData()
        // 解除监听state属性的函数集合
        const watchStateSet = new Set()
        // 监听依赖的state属性，响应对应方法
        watch(
          () => this.props.watchState,
          () => {
            watchStateSet.forEach((v: () => void) => v())

            this.props.watchState.forEach((watchObj: watchStateObj) => {
              if (watchObj.type == 'initData') {
                watchStateSet.add(watch(() => state[watchObj.stateProperty], initData))
              }
            })
          },
          { immediate: true, deep: true }
        )
        // 清空监听器
        onUnmounted(() => {
          watchStateSet.forEach((v: () => void) => v())
        })

        return () => <pComponent>{slots.default?.()}</pComponent>
      })
    )

    const pParams = this.params
    this.params = markRaw(
      defineComponent((props, { slots }) => {
        return () => (
          <pParams>
            {() => [
              <div class={classes['params-item']}>
                <ElText class={classes['params-item-label']} type="info">
                  数据url
                  <Tip
                    content={'可引用全局状态变量state的属性，例子：/abc/:def引用state.def'}
                  ></Tip>
                </ElText>
                <div class={classes['params-item-content']}>
                  <ElInput
                    modelValue={this.props.dataUrl}
                    onUpdate:modelValue={(v) => (this.props.dataUrl = v || '')}
                    size="small"
                  ></ElInput>
                </div>
              </div>,
              <div class={classes['params-item']}>
                <ElText class={classes['params-item-label']} type="info">
                  数据函数
                  <Tip
                    content={
                      '可访问的变量：节点自身_self；全局状态变量state；接口返回的数据resData；NList列表项数据itemData;至少返回一个数据，列表才有内容渲染'
                    }
                  ></Tip>
                </ElText>
                <div class={classes['params-item-content']}>
                  <ElSwitch v-model={this.props.dataFnable} size="small"></ElSwitch>
                  {this.props.dataFnable && (
                    <CodeEditor
                      modelValue={this.props.dataFn}
                      onUpdate:modelValue={(v) => (this.props.dataFn = v || '')}
                    ></CodeEditor>
                  )}
                </div>
              </div>,
              slots.default?.()
            ]}
          </pParams>
        )
      })
    )
  }

  toVue(): SfcObj {
    const sfcObj = super.toVue()
    const name = sfcObj.name
    const [uuid0] = this.uuid.split('-')
    const dataVar = `${name}_${uuid0}_data`
    const initDataVar = `${name}_${uuid0}_initData`

    sfcObj.dataVar = dataVar

    sfcObj.imports = {
      ...sfcObj.imports,
      vue: {
        ref: 'ref',
        watch: 'watch'
      },
      ['./imports/request.js']: {
        default: 'request'
      }
    }

    sfcObj.importFiles = {
      ...sfcObj.importFiles,
      'request.js': requestRaw
    }

    sfcObj.script = `
    ${sfcObj.script}
const ${dataVar} = ref([])
const ${initDataVar} = async () => {
  let resData = null

  ${
    this.props.dataUrl
      ? `await request({
    url: '${this.props.dataUrl}'.replace(/\\:(\\w+)/g, (match, p1) => state[p1]),
    method: 'GET'
  }).then((res) => {
    resData = (${this.props.dataFn || '() => null'})()
  })`
      : ''
  }

  ${this.props.dataFn ? `${dataVar}.value = (${this.props.dataFn || '() => null'})()` : ''}
}
${initDataVar}()
${
  this.props.watchState.length > 0
    ? `${this.props.watchState
        .map((obj) => {
          if (obj.type == 'initData')
            return `watch(() => state.${obj.stateProperty}, ${initDataVar})`
        })
        .join('\n')}`
    : ''
}`

    return sfcObj
  }
}
